/*
 * License: BSD
 * Authors: Walter Bright,
 *          Sean Kelly,
 *          Andrei Alexandrescu(www.erdani.org),
 *          Tomasz Stachowiak(IsExpressionTuple),
 *          Lucifer(786325481@QQ.com)
 * Copyright: Copyright (C) 2008 Lucifer. All rights reserved.
 */

/**
 * The traits module defines tools useful for obtaining detailed type information at compile-time.
 */
module system.Traits;

import std.typetuple;
/**
 * Get the type of the return value from a function,
 * a pointer to function, or a delegate.
 */
template ReturnType(alias dg)
{
    static if(is(typeof(dg)))
		alias ReturnType!(typeof(dg), void) ReturnType;
	else
		alias ReturnType!(dg, void) ReturnType;
}

template ReturnType(dg, dummy = void)
{
    static if (is(dg R == return))
		alias R ReturnType;
    else static if (is(dg T : T*))
		alias ReturnType!(T, void) ReturnType;
    else static if (is(dg S == struct))
		alias ReturnType!(typeof(&dg.opCall), void) ReturnType;
    else static if (is(dg C == class))
		alias ReturnType!(typeof(&dg.opCall), void) ReturnType;
    else
		static assert(0, "argument has no return type");
}

unittest
{
    struct G
    {
	int opCall (int i) { return 1;}
    }

    alias ReturnType!(G) ShouldBeInt;
    static assert(is(ShouldBeInt == int));

    G g;
    static assert(is(ReturnType!(g) == int));

    G* p;
    alias ReturnType!(p) pg;
    static assert(is(pg == int));

    class C
    {
	int opCall (int i) { return 1;}
    }

    static assert(is(ReturnType!(C) == int));

    C c;
    static assert(is(ReturnType!(c) == int));
}

/**
 * Get the types of the paramters to a function,
 * a pointer to function, or a delegate as a tuple.
 */
template ParameterTypeTuple(alias dg)
{
    alias ParameterTypeTuple!(typeof(dg)) ParameterTypeTuple;
}
template ParameterTypeTuple(dg)
{
    /*
     * Return a type tuple of function parameter types.
     * It is supported by complier.
     * See is expression in D Language Specification.
     */
    static if ( is(dg P == function) )
        alias P ParameterTypeTuple;
    else static if ( is(dg P == delegate) )
        alias ParameterTypeTuple!(P) ParameterTypeTuple;
    else static if ( is(dg P == P*) )
        alias ParameterTypeTuple!(P) ParameterTypeTuple;
    else
        static assert(false, "Argument " ~ T.stringof ~ "has no parameters.");
}

/**
 * Get the types of the fields of a struct or class.
 * This consists of the fields that take up memory space,
 * excluding the hidden fields like the virtual function
 * table pointer.
 */
template FieldTypeTuple(S)
{
    static if ( is ( S == struct ) || is ( S == class ) || is( S == union ) )
        alias typeof(S.tupleof) FieldTypeTuple;
    else
        alias S FieldTypeTuple;
        //static assert(false, "Argument " ~ T.stringof ~ "is not struct or class.");
}

/**
 * Get a TypeTuple of the base class and base interfaces of
 * this class or interface.
 */
template BaseTypeTuple(A)
{
    static if ( is ( A P == super) )
        alias P BaseTypeTuple;
    else
        static assert(false, "Argument " ~ T.stringof ~ "is not a class or interface.");
}

/**
 * Get a $(D_PARAM TypeTuple) of $(I all) base classes of this class,
 * in decreasing order. Interfaces are not included. $(D_PARAM
 * BaseClassesTuple!(Object)) yields the empty type tuple.
 *
 * Example:
 * ---
 * import system.traits, system.tuple;
 * interface I { }
 * class A { }
 * class B : A, I { }
 * class C : B { }
 *
 * void main()
 * {
 *     alias BaseClassesTuple!(C) TL;
 *     writeln(typeid(TL));	// prints: (B,A,Object)
 * }
 * ---
 */

template BaseClassesTuple(T)
{
    static if (is(T == Object))
    {
        alias TypeTuple!() BaseClassesTuple;
    }
    static if (is(BaseTypeTuple!(T)[0] == Object))
    {
        alias TypeTuple!(Object) BaseClassesTuple;
    }
    else
    {
        alias TypeTuple!(BaseTypeTuple!(T)[0],
                         BaseClassesTuple!(BaseTypeTuple!(T)[0]))
            BaseClassesTuple;
    }
}

unittest
{
    interface I1 {}
    interface I2 {}
    class B1 {}
    class B2 : B1, I1 {}
    class B3 : B2, I2 {}
    alias BaseClassesTuple!(B3) TL;
    assert(TL.length == 3);
    assert(is (TL[0] == B2));
    assert(is (TL[1] == B1));
    assert(is (TL[2] == Object));
}

/**
 * Get a $(D_PARAM TypeTuple) of $(I all) interfaces directly or
 * indirectly inherited by this class or interface. Interfaces do not
 * repeat if multiply implemented. $(D_PARAM InterfacesTuple!(Object))
 * yields the empty type tuple.
 *
 * Example:
 * ---
 * import std.traits, std.typetuple, std.stdio;
 * interface I1 { }
 * interface I2 { }
 * class A : I1, I2 { }
 * class B : A, I1 { }
 * class C : B { }
 *
 * void main()
 * {
 *     alias InterfacesTuple!(C) TL;
 *     writeln(typeid(TL));	// prints: (I1, I2)
 * }
 * ---
 */

template InterfacesTuple(T)
{
    static if (is(T == Object))
    {
        alias TypeTuple!() InterfacesTuple;
    }
    static if (is(BaseTypeTuple!(T)[0] == Object))
    {
        alias TypeTuple!(BaseTypeTuple!(T)[1 .. $]) InterfacesTuple;
    }
    else
    {
        alias NoDuplicates!(
            TypeTuple!(BaseTypeTuple!(T)[1 .. $], // direct interfaces
                       InterfacesTuple!(BaseTypeTuple!(T)[0])))
            InterfacesTuple;
    }
}

unittest
{
    interface I1 {}
    interface I2 {}
    {
        // doc example
        class A : I1, I2 { }
        class B : A, I1 { }
        class C : B { }
        alias InterfacesTuple!(C) TL;
        assert(is(TL[0] == I1) && is(TL[1] == I2));
    }
    class B1 : I1, I2 {}
    class B2 : B1, I1 {}
    class B3 : B2, I2 {}
    alias InterfacesTuple!(B3) TL;
    //
    assert(TL.length == 2);
    assert(is (TL[0] == I2));
    assert(is (TL[1] == I1));
}

/**
 * Get a $(D_PARAM TypeTuple) of $(I all) base classes of $(D_PARAM
 * T), in decreasing order, followed by $(D_PARAM T)'s
 * interfaces. $(D_PARAM TransitiveBaseTypeTuple!(Object)) yields the
 * empty type tuple.
 *
 * Example:
 * ---
 * import system.traits, system.tuple;
 * interface I { }
 * class A { }
 * class B : A, I { }
 * class C : B { }
 *
 * void main()
 * {
 *     alias TransitiveBaseTypeTuple!(C) TL;
 *     writeln(typeid(TL));	// prints: (B,A,Object,I)
 * }
 * ---
 */

template TransitiveBaseTuple(T)
{
    static if (is(T == Object))
        alias TypeTuple!() TransitiveBaseTuple;
    else
        alias TypeTuple!(BaseClassesTuple!(T),
            BaseTypeTuple!(T)[1 .. $])
            TransitiveBaseTuple;
}

unittest
{
    interface I1 {}
    class B1 {}
    class B2 : B1 {}
    class B3 : B2, I1 {}
    alias TransitiveBaseTuple!(B3) TL;
    assert(TL.length == 4);
    assert(is (TL[0] == B2));
    assert(is (TL[1] == B1));
    assert(is (TL[2] == Object));
    assert(is (TL[3] == I1));

    assert(TransitiveBaseTuple!(Object).length == 0);
}

/**
Get the type that all types can be implicitly converted to. Useful
e.g. in figuring out an array type from a bunch of initializing
values. Returns $(D_PARAM void) if passed an empty list, or if the
types have no common type.

Example:

----
alias CommonType!(int, long, short) X;
assert(is(X == long));
alias CommonType!(int, char[], short) Y;
assert(is(Y == void));
----
*/
template CommonType(T...)
{
    static if (!T.length)
        alias void CommonType;
    else static if (T.length == 1)
        alias T[0] CommonType;
    else static if (is(typeof(true ? T[0] : T[1]) U))
        alias CommonType!(U, T[2 .. $]) CommonType;
    else
        alias void CommonType;
}

unittest
{
    alias CommonType!(int, long, short) X;
    assert(is(X == long));
    alias CommonType!(char[], int, long, short) Y;
    assert(is(Y == void), Y.stringof);
}

/**
 * Returns a tuple with all possible target types of an implicit
 * conversion of a value of type $(D_PARAM T).
 *
 * Important note:
 *
 * The possible targets are computed more conservatively than the D
 * 2.005 compiler does, eliminating all dangerous conversions. For
 * example, $(D_PARAM ImplicitConversionTargets!(double)) does not
 * include $(D_PARAM float).
 */

template ImplicitConversionTargets(T)
{
    static if (is(T == bool))
        alias TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong,
            float, double, real, char, wchar, dchar)
            ImplicitConversionTargets;
    else static if (is(T == byte))
        alias TypeTuple!(short, ushort, int, uint, long, ulong,
            float, double, real, char, wchar, dchar)
            ImplicitConversionTargets;
    else static if (is(T == ubyte))
        alias TypeTuple!(short, ushort, int, uint, long, ulong,
            float, double, real, char, wchar, dchar)
            ImplicitConversionTargets;
    else static if (is(T == short))
        alias TypeTuple!(ushort, int, uint, long, ulong,
            float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == ushort))
        alias TypeTuple!(int, uint, long, ulong, float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == int))
        alias TypeTuple!(long, ulong, float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == uint))
        alias TypeTuple!(long, ulong, float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == long))
        alias TypeTuple!(float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == ulong))
        alias TypeTuple!(float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == float))
        alias TypeTuple!(double, real)
            ImplicitConversionTargets;
    else static if (is(T == double))
        alias TypeTuple!(real)
            ImplicitConversionTargets;
    else static if (is(T == char))
        alias TypeTuple!(wchar, dchar, byte, ubyte, short, ushort,
            int, uint, long, ulong, float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == wchar))
        alias TypeTuple!(wchar, dchar, short, ushort, int, uint, long, ulong,
            float, double, real)
            ImplicitConversionTargets;
    else static if (is(T == dchar))
        alias TypeTuple!(wchar, dchar, int, uint, long, ulong,
            float, double, real)
            ImplicitConversionTargets;
    else static if(is(T : Object))
        alias TransitiveBaseTuple!(T) ImplicitConversionTargets;
    else static if (is(T : void*))
        alias TypeTuple!(void*) ImplicitConversionTargets;
    else
        alias TypeTuple!() ImplicitConversionTargets;
}

unittest
{
    assert(is(ImplicitConversionTargets!(double)[0] == real));
}

/**
 * Detect whether T is one of the built-in string types
 */

template IsSomeString(T)
{
    static const IsSomeString = is(T : const(char)[])||
								is(T : const(wchar)[]) ||
								is(T : const(dchar)[]);
}

static assert(!IsSomeString!(int));
static assert(!IsSomeString!(int[]));
static assert(!IsSomeString!(byte[]));
static assert(IsSomeString!(char[]));
static assert(IsSomeString!(dchar[]));
static assert(IsSomeString!(string));
static assert(IsSomeString!(wstring));
static assert(IsSomeString!(dstring));
static assert(IsSomeString!(char[4]));

/**
 * Tell whether the tuple T is an expression tuple.
 */
template IsExpressionTuple(T)
{
    static if (is ( void function(T) ))
        const bool IsExpressionTuple = false;
    else
        const bool IsExpressionTuple = true;
}

/**
 * Detect whether type T is a static array.
 */
template IsStaticArray(T : U[N], U, size_t N)
{
    const bool IsStaticArray = true;
}

template IsStaticArray(T)
{
    const bool IsStaticArray = false;
}

static assert (IsStaticArray!(int[51]));
static assert (IsStaticArray!(int[][2]));
static assert (IsStaticArray!(char[][int][11]));
static assert (!IsStaticArray!(int[]));
static assert (!IsStaticArray!(int[char]));
static assert (!IsStaticArray!(int[1][]));
static assert (!IsStaticArray!(const(int)[]));
static assert (!IsStaticArray!(invariant(int)[]));
static assert (!IsStaticArray!(const(int)[4][]));
static assert (IsStaticArray!(invariant char[13u]));
static assert (IsStaticArray!(const(real)[1]));
static assert (IsStaticArray!(const(real)[1][1]));
//static assert (IsStaticArray!(typeof("string literal")));
static assert (IsStaticArray!(void[0]));
static assert (!IsStaticArray!(int[int]));
static assert (!IsStaticArray!(int));

/**
 * Detect whether type T is a dynamic array.
 */
template IsDynamicArray(T, U = void)
{
    static const IsDynamicArray = false;
}

template IsDynamicArray(T : U[], U)
{
    static const bool IsDynamicArray = !IsStaticArray!(T);
}
static assert (IsDynamicArray!(int[]));
static assert (IsDynamicArray!(int[][]));
static assert (!IsDynamicArray!(int[10]));

/**
 * Detect whether type T is an associate array.
 */
template IsAssociateArray(T)
{
    static const bool IsAssociateArray = is(typeof(T.keys)) && is(typeof(T.values));
}

static assert(IsAssociateArray!(int[string]));
static assert(IsAssociateArray!(invariant(char[5])[int]));
static assert(IsAssociateArray!(int[char[]]));
static assert (IsAssociateArray!(int[int]));
static assert (!IsAssociateArray!(int[10]));
static assert (!IsAssociateArray!(int[]));

/**
 * Detect whether type T is an array.
 */
template IsArray(T)
{
    static const IsArray = IsStaticArray!(T) || IsDynamicArray!(T);
}
static assert(IsArray!(int[]));
static assert(IsArray!(int[5]));
static assert(!IsArray!(uint));
static assert(!IsArray!(uint[uint]));
static assert(IsArray!(void[]));
static assert(IsArray!(typeof("ABCDEF")));

/**
 * Detect whether type T is a char type.
 */
template IsCharType(T)
{
    static const bool IsCharType = is( T == char ) ||
                                   is( T == wchar ) ||
                                   is( T == dchar );
}

/**
 * Detect whether type T is a signed integer type.
 */
template IsSignedIntegerType(T)
{
    //type cent is reserved for D language in future
    static const bool IsSignedIntegerType = is( T == byte ) ||
                                            is( T == short ) ||
                                            is( T == int ) ||
                                            is( T == long ) /* || is( T == cent ) */;
}

/**
 * Detect  whether type T is an unsigned integer type.
 */
template IsUnsignedIntegerType(T)
{
    //type ucent is reserved for future by D language
    static const bool IsUnsignedIntegerType = is( T == ubyte ) ||
                                              is( T == ushort ) ||
                                              is( T == uint ) ||
                                              is( T == ulong) /*|| is ( T == ucent ) */;
}

/**
 * Detect whether type T is an integer type.
 */
template IsIntegerType(T)
{
    static const bool IsIntegerType = IsSignedIntegerType!(T) || IsUnsignedIntegerType!(T);
}

/**
 * Detect whether type T is a real type.
 */
template IsRealType(T)
{
    static const bool IsRealType = is( T== float ) ||
                                   is( T == double ) ||
                                   is( T == real );
}

/**
 * Detect whether type T is an imaginary type.
 */
template IsImaginaryType(T)
{
    static const bool IsImaginaryType = is( T == ifloat ) ||
                                        is( T == idouble ) ||
                                        is( T == ireal );
}

/**
 * Detect whether type T is a complex type.
 */
template IsComplexType(T)
{
    static const bool IsComplexType = is( T == cfloat ) ||
                                      is( T == cdouble ) ||
                                      is( T == creal );
}

/**
 * Detect whether type T is floating-point type.
 */
template IsFloatingPointType(T)
{
    static const bool IsFloatingPointType = IsRealType!(T) ||
											IsImaginaryType!(T) ||
											IsComplexType!(T);
}

/**
 * Detect whether type T is a pointer type.
 */
template IsPointerType(T)
{
    static const bool IsPointerType = false;
}

template IsPointerType(T : T*)
{
	static const bool IsPointerType = true;
}

static assert(IsPointerType!(int*));
static assert(!IsPointerType!(int));
static assert(!IsPointerType!(string));
static assert(!IsPointerType!(wstring));
static assert(!IsPointerType!(dstring));
static assert(!IsPointerType!(char[]));
static assert(!IsPointerType!(wchar[]));
static assert(!IsPointerType!(dchar[]));

/**
 * Detect whether type T is a reference type.
 */
template IsReferenceType(T)
{
    static const bool IsReferenceType = is( T == class ) ||
                                        is( T == interface ) ||
                                        is( T == delegate ) ||
										IsArray!(T) ||
                                        IsPointerType!(T);
}

/**
 * Detect whether type T is a callable type.
 */
template IsCallableType(T)
{
    static const bool IsCallableType = is( T == function ) ||
                                       is( typeof(*T) == function ) ||
                                       is( T == delegate ) ||
                                       is( typeof(T.opCall) == function );
}

/**
 * Detect whether type T is a primitive type.
 */
template IsPrimitiveType(T)
{
    static const bool IsPrimitiveType = IsIntegerType!(T) ||
                                        IsFloatingPointType!(T) ||
                                        IsCharType!(T) ||
                                        is( T == bool ) ||
                                        is( T == void );

}

/**
 * Detect whether type T is a numeric type.
 */
template IsNumericType(T)
{
    static const bool IsNumericType = IsIntegerType!(T) || IsFloatingPointType!(T);
}

/**
 * Returns the corresponding unsigned type for T. T must be a numeric
 * integral type, otherwise a compile-time error occurs.
 */

template Unsigned(T)
{
  static if (is(T == byte)) alias ubyte Unsigned;
  else static if (is(T == short)) alias ushort Unsigned;
  else static if (is(T == int)) alias uint Unsigned;
  else static if (is(T == long)) alias ulong Unsigned;
  else static if (is(T == ubyte)) alias ubyte Unsigned;
  else static if (is(T == ushort)) alias ushort Unsigned;
  else static if (is(T == uint)) alias uint Unsigned;
  else static if (is(T == ulong)) alias ulong Unsigned;
  else static if(is(T == enum))
  {
       static if (T.sizeof == 1) alias ubyte Unsigned;
       else static if (T.sizeof == 2) alias ushort Unsigned;
       else static if (T.sizeof == 4) alias uint Unsigned;
       else static if (T.sizeof == 8) alias ulong Unsigned;
  }
  else static assert(false, "Type " ~ T.stringof
                     ~ " does not have an unsigned counterpart");
}
unittest
{
    alias Unsigned!(int) U;
    assert(is(U == uint));
}
